from machine import Pin
from utime import ticks_ms, ticks_us, sleep_us
from others import PinS

class HC_SR04Exception(BaseException):
	pass

class HC_SR04:
	# HC-SR04 超声波测距模块驱动程序

	u"""
	HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能，测距精度可达高到 3mm
	模块包括超声波发射器、接收器与控制电路。

	基本工作原理：
		（1）采用 IO 口 TRIG 触发测距，给最少 10us 的高电平信呈。
		（2）模块自动发送 8 个 40khz 的方波，自动检测是否有信号返回；
		（3）有信号返回，通过 IO 口 ECHO 输出一个高电平，高电平持续的时间就是超声波从发射到返回的时间。
		（4）测试距离=（高电平时间*声速（340M/S））/2;
		（5）该模块必须使用 5V 供电
	"""

	# 定义打开 echo 针脚超时返回值常量
	_OPEN_ECHO_TIMEOUT_RESULT = -1, -1, -1, -1

	# 定义关闭 echo 针脚超时返回值常量
	_CLOSE_ECHO_TIMEOUT_RESULT = -2, -2, -2, -2

	# 定义打开、关闭 echo 针脚的超时时间
	_OPEN_CLOSE_TIMEOUT = 100

	# 定义超声波在空气中的传播速度常量，大约 340米/秒，温度越高传播速度越快
	_ECHO_SPEED = 340

	# 定义 trig 针脚并赋初值
	_trig = None

	# 定义 echo 针脚并赋初值
	_echo = None

	def __init__(self):
		u"""
		实例化 HC-SR04 驱动
		"""

		pass

	def init(self, trig: PinS.GPIO, echo: PinS.GPIO):
		u"""
		初始化 HC-SR04 驱动
		:param trig: 指定 trig 针脚的 gpio 编号
		:param echo: 指定 echo 针脚的 gpio 编号
		:return: 无
		"""

		self._trig = Pin(trig, Pin.OUT)
		self._echo = Pin(echo, Pin.IN)

	def deinit(self):
		u"""
		释放驱动程序使用的 trig 和 echo 针脚
		:return: 无
		"""
		self._trig = None
		self._echo = None

	def getDistance(self):
		u"""
		获取测距结果数据
		:return:  m - 以 米 为单位的距离
				 dm - 以 分米 为单位的距离
				 cm - 以 厘米 为单位的距离
				 mm - 以 毫米 为单位的距离
		"""

		# 如果 trig 针脚或 echo 针脚未绑定到 gpio，则抛出异常提示
		if (self._trig is None or self._echo is None):
			raise HC_SR04Exception("Pin trig or echo not initialized")

		# 定义返回值并赋初值
		m = dm = cm = mm = 0

		u"""
		给 trig 针脚持续 20微秒 高电平后关闭，开始发射超声波
		同时 echo 针脚将自动打开，用于接收回声
		"""
		self._trig.on()
		sleep_us(20)
		self._trig.off()

		# 定义超时开始时间，单位：毫秒
		time_out_ms = ticks_ms()

		u"""
		等待 echo 针脚自动打开
		如果 self.echo.value() == 0，表示 echo 针脚未打开，需要继续等待
		"""
		while (self._echo.value() == 0):
			u"""
			echo 针脚也许会打开失败导致程序假死
			所以在等待 echo 针脚打开的同时还要设置一个超时时间
			如果超时（100毫秒）就结束函数体的运行
			并返回一个常量告知超时原因
			"""
			if ((ticks_ms() - time_out_ms) > self._OPEN_CLOSE_TIMEOUT):
				return self._OPEN_ECHO_TIMEOUT_RESULT
			pass

		u"""
		如果 self.echo.value() == 1，表示 echo 针脚已打开，可以开始接收回声了
		"""
		if (self._echo.value() == 1):
			# 定义 echo 针脚打开的开始时间，单位：微秒
			time_start = ticks_us()

			# 定义超时开始时间，单位：毫秒
			time_out_ms = ticks_ms()

			u"""
			等待 echo 针脚关闭
			如果 self.echo.value() == 1，表示 echo 针脚还是打开状态，并未完成回声的接收
			"""
			while (self._echo.value() == 1):
				u"""
				echo 针脚也许会关闭失败导致程序假死
				所以在等待 echo 针脚关闭的同时还要设置一个超时时间
				如果超时（100毫秒）就结束函数体的运行
				并返回一个常量告知超时原因
				"""
				if ((ticks_ms() - time_out_ms) > self._OPEN_CLOSE_TIMEOUT):
					return self._CLOSE_ECHO_TIMEOUT_RESULT
				pass

			# 至此，echo 针脚已完成回声的接收，定义 超声波来回一次的时间差，单位：微秒
			time_response = ticks_us() - time_start

			u"""
			测距公式为：L = C * T
				L：测量距离的长度
				C：超声波在空气中的传播速度
				T：超声波测距传播的单程时间，即 超声波来回一次的时间差 的一半

			由于超声波在空气中的传播速度单位为 米/秒，而测距传播的单程时间单位为 微秒
			为了转换为我们常用的距离单位 米，需要把测距的长度除以 1000 得到毫秒级的数值
			再除以 1000 得到秒级的数值，进而匹配超声波传播速度的单位 米/秒
			"""

			# 定义测距结果，单位：米
			distance = ((time_response / 2) * self._ECHO_SPEED) / 1000 / 1000

			# 将各种单位的数值赋值给函数返回值
			m = distance
			dm = m * 10
			cm = dm * 10
			mm = cm * 10

		return m, dm, cm, mm